<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue';
import WaveSurfer from 'wavesurfer.js';
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.js';
import Hover from 'wavesurfer.js/dist/plugins/hover.js';

type Position = { x: number; y: number; z: number };
type Direction = '+X' | '-X' | '+Y' | '-Y' | '+Z' | '-Z';

// --- Event System Types ---
type EventType = 'CameraEvent' | 'SceneryEvent' | 'ObstacleEvent' | 'CustomEvent';
type EventParams = Record<string, string | number | object>;

// Each event now has its own unique ID for list management
type LevelEvent = {
  id: string;
  eventType: EventType;
  params: EventParams;
};

type Region = {
  id: string;
  start: number;
  content: string;
  position: Position;
  direction: Direction;
  tag: string;
  judgementWindow: number;
  // A point can now have multiple events
  levelEvents: LevelEvent[];
};

// DOM Refs
const waveformRef = ref<HTMLElement | null>(null);
const fileInput = ref<HTMLInputElement | null>(null);
const dropdownRef = ref<HTMLElement | null>(null);

// WaveSurfer and other instances (non-reactive)
let ws: WaveSurfer | null = null;
let wsRegions: ReturnType<typeof RegionsPlugin.create> | null = null;
let currentObjectUrl: string | null = null;

// --- Game Logic State ---
const speed = ref(3); // units per second
const traceStep = ref(10); // New state for trace granularity
const defaultAlternatingDirections: Direction[] = ['+Z', '+X'];
const cycleDirections: Direction[] = ['+X', '-X', '+Y', '-Y', '+Z', '-Z'];

// --- Event System Refactor: Data-driven configuration ---
const eventDefinitions = [
  {
    type: 'CameraEvent' as EventType,
    defaultPayload: { animation: '', duration: 2.0 },
  },
  {
    type: 'SceneryEvent' as EventType,
    defaultPayload: { sceneryID: '', action: '' },
  },
  {
    type: 'ObstacleEvent' as EventType,
    defaultPayload: { obstacleID: '', action: '' },
  },
  {
    type: 'CustomEvent' as EventType,
    defaultPayload: { key: 'value' },
  },
];

const eventTypeConfig = eventDefinitions.reduce(
  (config, def) => {
    config[def.type] = {
      payload: {
        component: 'textarea',
        type: 'text',
        default: JSON.stringify(def.defaultPayload, null, 2),
        description: '消息数据，使用JSON格式',
      },
    };
    return config;
  },
  {} as Record<
    EventType,
    Record<
      string,
      {
        component: 'input' | 'textarea';
        type: 'text' | 'number';
        default: string | number;
        description?: string;
      }
    >
  >
);

// --- UI State ---
const regions = ref<Region[]>([]);
const isLoading = ref(false);
const isPlaying = ref(false);
const loadingProgress = ref(0);
const fileName = ref('');
const currentTime = ref(0);
const totalDuration = ref(0);
const volume = ref(0.75);

const formattedCurrentTime = computed(() => formatTime(currentTime.value));
const formattedTotalDuration = computed(() => formatTime(totalDuration.value));
const isReady = computed(() => !isLoading.value && fileName.value !== '');

const timelineMarks = computed(() => {
  if (!isReady.value || totalDuration.value === 0) return [];
  const marks = [];
  const step = totalDuration.value / 10;
  for (let i = 0; i <= 10; i++) {
    const time = step * i;
    marks.push({
      time: formatTime(time, false),
      position: `${i * 10}%`,
      percentage: `${i * 10}%`,
    });
  }
  return marks;
});

// --- Event Modal State ---
const isEventModalOpen = ref(false);
const editingRegion = ref<Region | null>(null);
const isDropdownOpen = ref(false);
const modalEventListRef = ref<HTMLElement | null>(null);

// --- Core Logic ---

const directionVectors: Record<Direction, [number, number, number]> = {
  '+X': [1, 0, 0],
  '-X': [-1, 0, 0],
  '+Y': [0, 1, 0],
  '-Y': [0, -1, 0],
  '+Z': [0, 0, 1],
  '-Z': [0, 0, -1],
};

function isPayloadValid(payload: any) {
  if (typeof payload !== 'string' || !payload.trim()) return true; // Empty is valid
  try {
    JSON.parse(payload);
    return true;
  } catch (e) {
    return false;
  }
}

function regionHasErrors(region: Region): boolean {
  return region.levelEvents.some((event) => !isPayloadValid(event.params.payload));
}

function recalculateAllPositions() {
  if (regions.value.length === 0) return;

  regions.value.forEach((region, index) => {
    if (index === 0) {
      region.position = { x: 0, y: 0, z: 0 };
    } else {
      const prevRegion = regions.value[index - 1];
      const deltaTime = region.start - prevRegion.start;
      const distance = speed.value * deltaTime;
      const vector = directionVectors[prevRegion.direction];

      region.position = {
        x: prevRegion.position.x + vector[0] * distance,
        y: prevRegion.position.y + vector[1] * distance,
        z: prevRegion.position.z + vector[2] * distance,
      };
    }
  });
}

function addRegionAt(time: number) {
  if (!ws || !wsRegions) return;

  wsRegions.addRegion({
    start: time,
    end: time + 0.01,
    resize: false,
    color: 'rgba(255, 102, 0, 0.3)',
  });
}

function changeRegionDirection(regionId: string) {
  const region = regions.value.find((r) => r.id === regionId);
  if (!region) return;

  const currentIndex = cycleDirections.indexOf(region.direction);
  const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % cycleDirections.length;
  region.direction = cycleDirections[nextIndex];

  recalculateAllPositions();
}

onMounted(() => {
  if (!waveformRef.value) return;

  ws = WaveSurfer.create({
    container: waveformRef.value,
    waveColor: '#a8a8a8',
    progressColor: '#ff6600',
    barWidth: 3,
    barRadius: 2,
    height: 150,
    cursorColor: '#ff6600',
  });

  wsRegions = ws.registerPlugin(RegionsPlugin.create());

  ws.registerPlugin(
    Hover.create({
      lineColor: '#ffffff',
      lineWidth: 2,
      labelBackground: '#ffffff',
      labelColor: '#000000',
      labelSize: '11px',
    })
  );

  const wrapper = ws.getWrapper();
  wrapper.addEventListener('contextmenu', (e: MouseEvent) => {
    e.preventDefault();
    if (!ws || isLoading.value || !totalDuration.value) return;

    const bbox = wrapper.getBoundingClientRect();
    const progress = (e.clientX - bbox.left) / bbox.width;
    const time = progress * totalDuration.value;
    addRegionAt(time);
  });

  ws.on('loading', (percent) => (loadingProgress.value = percent));
  ws.on('ready', (duration) => {
    isLoading.value = false;
    totalDuration.value = duration;
  });
  ws.on('play', () => (isPlaying.value = true));
  ws.on('pause', () => (isPlaying.value = false));
  ws.on('timeupdate', (time) => (currentTime.value = time));
  ws.on('finish', () => {
    isPlaying.value = false;
    ws?.seekTo(0);
  });

  const setupRegion = (region: any) => {
    const content = formatTime(region.start);
    region.setContent(content);

    const lastRegion = regions.value[regions.value.length - 1];
    let newDirection = defaultAlternatingDirections[0];
    if (lastRegion) {
      const lastDirectionIndex = defaultAlternatingDirections.indexOf(lastRegion.direction);
      newDirection =
        defaultAlternatingDirections[
          (lastDirectionIndex + 1) % defaultAlternatingDirections.length
        ];
    }

    return {
      id: region.id,
      start: region.start,
      content,
      position: { x: 0, y: 0, z: 0 },
      direction: newDirection,
      tag: '1',
      judgementWindow: 0.2,
      levelEvents: [], // Initialize with empty events array
    };
  };

  wsRegions.on('region-created', (region) => {
    const newRegion = setupRegion(region);
    regions.value.push(newRegion);
    regions.value.sort((a, b) => a.start - b.start);
    nextTick(() => recalculateAllPositions());
  });

  wsRegions.on('region-updated', (region) => {
    const index = regions.value.findIndex((r) => r.id === region.id);
    if (index !== -1) {
      regions.value[index].start = region.start;
      regions.value[index].content = formatTime(region.start);
      regions.value.sort((a, b) => a.start - b.start);
      nextTick(() => recalculateAllPositions());
    }
  });

  wsRegions.on('region-removed', (region) => {
    regions.value = regions.value.filter((r) => r.id !== region.id);
    nextTick(() => recalculateAllPositions());
  });

  ws.setVolume(volume.value);
  window.addEventListener('keydown', handleKeyDown);
});

onUnmounted(() => {
  window.removeEventListener('keydown', handleKeyDown);
  ws?.destroy();
});

watch(volume, (newVolume) => ws?.setVolume(newVolume));
watch(speed, recalculateAllPositions);

function handleKeyDown(e: KeyboardEvent) {
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
    return; // Ignore keydown if focused on an input
  }
  if (e.code === 'Space') {
    e.preventDefault();
    togglePlayPause();
  } else if (e.code === 'KeyS' && isReady.value) {
    e.preventDefault();
    if (!ws) return;
    addRegionAt(ws.getCurrentTime());
  }
}

function handleFileUpload(event: Event) {
  const target = event.target as HTMLInputElement;
  const file = target.files?.[0];
  if (!file || !ws) return;

  ws.stop();
  if (currentObjectUrl) {
    URL.revokeObjectURL(currentObjectUrl);
    currentObjectUrl = null;
  }

  regions.value = [];
  ws.empty();
  wsRegions?.clearRegions();

  fileName.value = file.name;
  isLoading.value = true;
  loadingProgress.value = 0;
  totalDuration.value = 0;
  currentTime.value = 0;

  currentObjectUrl = URL.createObjectURL(file);
  ws.load(currentObjectUrl);
}

function removeRegion(regionId: string) {
  wsRegions
    ?.getRegions()
    .find((r) => r.id === regionId)
    ?.remove();
}

function seekTo(timeInSeconds: number) {
  if (!ws || totalDuration.value === 0) return;
  ws.seekTo(timeInSeconds / totalDuration.value);
}

function togglePlayPause() {
  if (!isReady.value) return;
  ws?.playPause();
}

function formatTime(seconds: number, withMilliseconds: boolean = true): string {
  const m = Math.floor(seconds / 60);
  const s = Math.floor(seconds % 60);
  const ms = Math.floor((seconds * 1000) % 1000);
  return withMilliseconds
    ? `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}.${String(ms).padStart(3, '0')}`
    : `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}

function exportRegionsToJson() {
  // First, validate all payloads
  for (const region of regions.value) {
    for (const event of region.levelEvents) {
      if (!isPayloadValid(event.params.payload)) {
        alert(
          `错误：时间点 ${formatTime(region.start)} 的事件 (${event.eventType}) 包含无效的JSON。请修正后再导出。`
        );
        return; // Abort export
      }
    }
  }

  const allLevelEvents = regions.value.flatMap((r) =>
    r.levelEvents.map((event) => {
      const finalParams = { ...event.params };
      if (typeof finalParams.payload === 'string' && finalParams.payload.trim()) {
        try {
          finalParams.payload = JSON.parse(finalParams.payload);
        } catch (e) {
          // This should not happen due to the validation above, but as a safeguard
          console.warn(`Could not parse payload for event at time ${r.start}`);
        }
      }
      return {
        time: r.start,
        eventType: event.eventType,
        params: finalParams,
      };
    })
  );

  const exportData = {
    songName: fileName.value,
    speed: speed.value,
    totalDuration: totalDuration.value,
    turnPoints: regions.value.map((r) => ({
      time: r.content,
      timeInSeconds: r.start,
      position: r.position,
      direction: r.direction,
      tag: r.tag,
      judgementWindow: r.judgementWindow,
    })),
    levelEvents: allLevelEvents,
    fullPathTrace: [] as { percentage: number; time: number; position: Position }[],
  };

  if (totalDuration.value > 0 && regions.value.length > 0) {
    const trace = [];
    for (let p = 0; p <= 100; p += traceStep.value) {
      const percentage = p;
      const time = (totalDuration.value * percentage) / 100;
      const prevRegion = [...regions.value].reverse().find((r) => r.start <= time);
      let position: Position;
      if (!prevRegion) {
        position = { x: 0, y: 0, z: 0 };
      } else {
        const deltaTime = time - prevRegion.start;
        const distance = speed.value * deltaTime;
        const vector = directionVectors[prevRegion.direction];
        position = {
          x: prevRegion.position.x + vector[0] * distance,
          y: prevRegion.position.y + vector[1] * distance,
          z: prevRegion.position.z + vector[2] * distance,
        };
      }
      trace.push({ percentage, time, position });
    }
    exportData.fullPathTrace = trace;
  }

  const jsonString = JSON.stringify(exportData, null, 2);
  const blob = new Blob([jsonString], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${fileName.value.replace(/\.[^/.]+$/, '') || 'level-data'}.json`;
  a.click();
  URL.revokeObjectURL(url);
  a.remove();
}

function triggerFileUpload() {
  fileInput.value?.click();
}

function isDirectionConflict(index: number): boolean {
  if (index === 0) return false;
  const prevRegion = regions.value[index - 1];
  const currentRegion = regions.value[index];
  if (!prevRegion || !currentRegion) return false;
  return prevRegion.direction === currentRegion.direction;
}

const getIconTransform = (direction: Direction) => {
  const rotations: { [key in Direction]: number } = {
    '+X': -135,
    '-X': 45,
    '+Y': -90,
    '-Y': 90,
    '+Z': -45,
    '-Z': 135,
  };
  return `rotate(${rotations[direction]} 10 10)`;
};

// --- Event Modal Logic & Listeners ---
const handleEsc = (e: KeyboardEvent) => {
  if (e.key === 'Escape') closeEventModal();
};

watch(isEventModalOpen, (isOpen) => {
  if (isOpen) {
    window.addEventListener('keydown', handleEsc);
  } else {
    window.removeEventListener('keydown', handleEsc);
  }
});

const handleClickOutsideDropdown = (event: MouseEvent) => {
  if (dropdownRef.value && !dropdownRef.value.contains(event.target as Node)) {
    isDropdownOpen.value = false;
  }
};

watch(isDropdownOpen, (isOpen) => {
  if (isOpen) {
    document.addEventListener('click', handleClickOutsideDropdown);
  } else {
    document.removeEventListener('click', handleClickOutsideDropdown);
  }
});

function openEventModal(regionId: string) {
  editingRegion.value = regions.value.find((r) => r.id === regionId) || null;
  isEventModalOpen.value = true;
}

function closeEventModal() {
  isEventModalOpen.value = false;
  editingRegion.value = null;
  isDropdownOpen.value = false;
}

async function addNewEvent(type: EventType) {
  if (!editingRegion.value) return;
  const newEvent: LevelEvent = {
    id: `evt_${Date.now()}_${Math.random()}`,
    eventType: type,
    params: Object.entries(eventTypeConfig[type]).reduce((acc, [key, conf]) => {
      acc[key] = conf.default;
      return acc;
    }, {} as EventParams),
  };
  editingRegion.value.levelEvents.push(newEvent);

  await nextTick();
  if (modalEventListRef.value) {
    modalEventListRef.value.scrollTo({
      top: modalEventListRef.value.scrollHeight,
      behavior: 'smooth',
    });
  }
  isDropdownOpen.value = false;
}

function removeEvent(eventId: string) {
  if (!editingRegion.value) return;
  editingRegion.value.levelEvents = editingRegion.value.levelEvents.filter((e) => e.id !== eventId);
}
</script>

<template>
  <div class="analyzer-container">
    <header class="app-header">
      <h1>《跳舞的线》路径生成器</h1>
      <p>上传MP3，右键点击波形图添加路径点（或按S键），并为路径点添加关卡事件。</p>
    </header>

    <main class="content-wrapper">
      <div class="waveform-section">
        <div class="waveform-wrapper">
          <div v-if="isLoading" class="loading-overlay">
            <p>加载中 "{{ fileName }}"...</p>
            <div class="progress-bar">
              <div class="progress" :style="{ width: loadingProgress + '%' }"></div>
            </div>
            <span>{{ loadingProgress }}%</span>
          </div>
          <div ref="waveformRef" class="waveform-container"></div>
          <div
            v-if="!isReady && !isLoading"
            class="placeholder-container"
            @click="triggerFileUpload"
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="64"
              height="64"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="1"
              stroke-linecap="round"
              stroke-linejoin="round"
              class="placeholder-icon"
            >
              <path d="M9 18V5l12-2v13"></path>
              <circle cx="6" cy="18" r="3"></circle>
              <circle cx="18" cy="16" r="3"></circle>
            </svg>
            <p class="placeholder-text">点击或拖拽一个MP3文件以开始</p>
          </div>
        </div>
        <div v-if="isReady" class="timeline">
          <div
            v-for="(mark, index) in timelineMarks"
            :key="index"
            class="timeline-mark"
            :style="{ left: mark.position }"
          >
            <span class="timeline-percentage">{{ mark.percentage }}</span>
            <span class="timeline-time">{{ mark.time }}</span>
          </div>
        </div>
      </div>

      <div class="controls-area">
        <div class="control-group player-controls">
          <button
            @click="togglePlayPause"
            class="play-pause-btn"
            :disabled="!isReady"
            :aria-label="isPlaying ? '暂停' : '播放'"
          >
            <svg
              v-if="!isPlaying"
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="24"
              height="24"
            >
              <path d="M8 5v14l11-7z"></path>
            </svg>
            <svg
              v-else
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="24"
              height="24"
            >
              <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path>
            </svg>
          </button>
          <div class="time-display" v-if="isReady">
            <span>{{ formattedCurrentTime }}</span> / <span>{{ formattedTotalDuration }}</span>
          </div>
          <div class="time-display" v-else><span>00:00.000</span> / <span>00:00.000</span></div>
          <div class="volume-control">
            <svg
              v-if="volume > 0.5"
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="24"
              height="24"
            >
              <path
                d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"
              ></path>
            </svg>
            <svg
              v-else-if="volume > 0"
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="24"
              height="24"
            >
              <path
                d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"
              ></path>
            </svg>
            <svg
              v-else
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="24"
              height="24"
            >
              <path d="M7 9v6h4l5 5V4L11 9H7z"></path>
            </svg>
            <input
              type="range"
              min="0"
              max="1"
              step="0.05"
              v-model="volume"
              class="volume-slider"
              :disabled="!isReady"
            />
          </div>
        </div>

        <div class="control-group game-settings">
          <div class="setting-control">
            <label for="speed-input">移动速度 (u/s)</label>
            <input
              id="speed-input"
              type="number"
              v-model.number="speed"
              min="1"
              class="settings-input"
            />
          </div>
          <div class="setting-control">
            <label for="trace-step-input">路径追踪步长 (%)</label>
            <input
              id="trace-step-input"
              type="number"
              v-model.number="traceStep"
              min="1"
              max="25"
              class="settings-input"
            />
          </div>
        </div>

        <div class="control-group file-actions">
          <input
            type="file"
            ref="fileInput"
            @change="handleFileUpload"
            accept="audio/mp3"
            style="display: none"
          />
          <button @click="triggerFileUpload" class="action-btn">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="18"
              height="18"
            >
              <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"></path>
            </svg>
            <span>上传MP3</span>
          </button>
          <button @click="exportRegionsToJson" :disabled="regions.length === 0" class="action-btn">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              fill="currentColor"
              width="18"
              height="18"
            >
              <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
            </svg>
            <span>导出JSON</span>
          </button>
        </div>
      </div>

      <div v-if="regions.length > 0" class="regions-list-container">
        <h2>路径点与事件</h2>
        <ul class="regions-list">
          <li
            v-for="(region, index) in regions"
            :key="region.id"
            class="region-item"
            :class="{
              'conflict-item': isDirectionConflict(index),
              'has-error': regionHasErrors(region),
            }"
          >
            <div class="region-main-info" @click="seekTo(region.start)">
              <span class="region-index">{{ index + 1 }}</span>
              <div class="region-details">
                <span class="timestamp">{{ region.content }}</span>
                <div class="coords">
                  <span>X: {{ region.position.x.toFixed(2) }}</span>
                  <span>Y: {{ region.position.y.toFixed(2) }}</span>
                  <span>Z: {{ region.position.z.toFixed(2) }}</span>
                </div>
              </div>
            </div>

            <div class="region-side-controls">
              <input
                v-model="region.tag"
                class="tag-input"
                @click.stop
                @keydown.stop
                aria-label="路径点标识"
              />
              <div class="setting-control region-setting">
                <label :for="`judgement-${region.id}`">判定窗(s)</label>
                <input
                  :id="`judgement-${region.id}`"
                  v-model.number="region.judgementWindow"
                  type="number"
                  step="0.05"
                  min="0"
                  class="settings-input"
                  @click.stop
                  @keydown.stop
                />
              </div>
              <button
                @click.stop="changeRegionDirection(region.id)"
                class="direction-btn-list"
                aria-label="改变方向"
              >
                <svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                  <path
                    d="M0 9 L15 9 L15 6 L20 10 L15 14 L15 11 L0 11 Z"
                    fill="currentColor"
                    :transform="getIconTransform(region.direction)"
                  ></path>
                </svg>
                <span>{{ region.direction }}</span>
              </button>
            </div>

            <div class="region-event-area">
              <div class="event-tags">
                <span v-if="regionHasErrors(region)" class="error-indicator">⚠️</span>
                <span v-for="event in region.levelEvents" :key="event.id" class="event-tag">
                  {{ event.eventType }}
                </span>
                <span v-if="region.levelEvents.length === 0" class="no-events-text"> 无事件 </span>
              </div>
              <button @click.stop="openEventModal(region.id)" class="manage-events-btn">
                管理事件 ({{ region.levelEvents.length }})
              </button>
            </div>

            <button
              @click.stop="removeRegion(region.id)"
              class="delete-btn"
              aria-label="移除路径点"
            >
              ×
            </button>
          </li>
        </ul>
      </div>
    </main>

    <!-- Event Editor Modal -->
    <Transition name="modal-fade">
      <div
        v-if="isEventModalOpen && editingRegion"
        class="modal-overlay"
        @click.self="closeEventModal"
      >
        <div class="modal-content">
          <div class="modal-header">
            <h3>
              管理事件：
              <span class="modal-time-highlight">{{ formatTime(editingRegion.start) }}</span>
            </h3>
            <button @click="closeEventModal" class="close-modal-btn" aria-label="关闭">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="currentColor"
                width="24"
                height="24"
              >
                <path
                  d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
                ></path>
              </svg>
            </button>
          </div>

          <div class="modal-event-list" ref="modalEventListRef">
            <div v-if="editingRegion.levelEvents.length === 0" class="no-events-modal">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="48"
                height="48"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="1"
                stroke-linecap="round"
                stroke-linejoin="round"
              >
                <path
                  d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"
                ></path>
                <path d="m16 12-4-4-4 4"></path>
                <path d="M12 16V8"></path>
              </svg>
              <p>此路径点尚无事件。</p>
              <p>从下方的"添加新事件"菜单中选择一个来开始。</p>
            </div>
            <div
              v-for="event in editingRegion.levelEvents"
              :key="event.id"
              class="modal-event-item"
            >
              <div class="modal-event-header">
                <h4>{{ event.eventType }}</h4>
                <button
                  @click="removeEvent(event.id)"
                  class="remove-event-item-btn"
                  aria-label="删除此事件"
                >
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    viewBox="0 0 24 24"
                    fill="currentColor"
                    width="20"
                    height="20"
                  >
                    <path
                      d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
                    ></path>
                  </svg>
                </button>
              </div>
              <div
                class="form-group"
                v-for="(config, key) in eventTypeConfig[event.eventType]"
                :key="key"
              >
                <label :for="`param-${event.id}-${key}`">{{ key }}</label>
                <input
                  v-if="config.component === 'input'"
                  v-model="event.params[key]"
                  :type="config.type"
                  :id="`param-${event.id}-${key}`"
                  class="modal-input"
                />
                <textarea
                  v-if="config.component === 'textarea'"
                  v-model="(event.params as any)[key]"
                  :id="`param-${event.id}-${key}`"
                  class="modal-textarea"
                  :class="{ 'invalid-json': !isPayloadValid(event.params.payload) }"
                  rows="5"
                ></textarea>
                <p
                  v-if="key === 'payload' && !isPayloadValid(event.params.payload)"
                  class="payload-error"
                >
                  错误：内容不是有效的JSON格式。
                </p>
              </div>
            </div>
          </div>
          <div class="modal-actions">
            <div class="dropdown" :class="{ 'is-active': isDropdownOpen }" ref="dropdownRef">
              <button
                class="modal-btn modal-btn-primary add-event-btn"
                @click="isDropdownOpen = !isDropdownOpen"
              >
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  viewBox="0 0 24 24"
                  fill="currentColor"
                  width="18"
                  height="18"
                >
                  <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path>
                </svg>
                添加新事件
              </button>
              <div class="dropdown-content">
                <a v-for="def in eventDefinitions" :key="def.type" @click="addNewEvent(def.type)">
                  {{ def.type }}
                </a>
              </div>
            </div>
            <button @click="closeEventModal" class="modal-btn modal-btn-secondary">完成</button>
          </div>
        </div>
      </div>
    </Transition>
  </div>
</template>

<style scoped>
:root {
  --primary-color: #ff6600;
  --primary-hover-color: #e65c00;
  --background-color: #1e1e1e;
  --surface-color: #2a2a2a;
  --text-color: #e0e0e0;
  --text-muted-color: #a0a0a0;
  --border-color: #3a3a3a;
  --danger-color: #ef4444;
  --danger-hover-color: #dc2626;
}

.analyzer-container {
  background-color: var(--background-color, #1e1e1e);
  color: var(--text-color, #e0e0e0);
  min-height: 100vh;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding-top: 2rem;
}

.app-header {
  text-align: center;
  margin-bottom: 2rem;
  padding: 0 1rem;
}
.app-header h1 {
  color: #fff;
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.5px;
}
.app-header p {
  color: var(--text-muted-color, #a0a0a0);
  max-width: 600px;
  margin: 0.5rem auto 0;
  font-size: 1.05rem;
}

.content-wrapper {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
  padding: 0 2rem 2rem;
  flex-grow: 1;
}

.waveform-section {
  background-color: var(--surface-color, #2a2a2a);
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border-color);
}

.waveform-wrapper {
  position: relative;
  background: #1a1a1a;
  border-radius: 8px;
  min-height: 150px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.waveform-container {
  width: 100%;
}

.timeline {
  position: relative;
  width: 100%;
  height: 30px;
  margin-top: 12px;
  border-top: 1px solid var(--border-color);
  padding-top: 8px;
}
.timeline-mark {
  position: absolute;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: translateX(-50%);
}
.timeline-mark::before {
  content: '';
  width: 1px;
  height: 6px;
  background: var(--text-muted-color);
  display: block;
  margin-bottom: 4px;
}
.timeline-percentage {
  font-size: 11px;
  color: var(--text-color);
  font-weight: 500;
}
.timeline-time {
  font-size: 10px;
  color: var(--text-muted-color);
  padding-top: 2px;
}

.placeholder-container {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  color: #666;
  cursor: pointer;
  border: 2px dashed #444;
  border-radius: 8px;
  transition: all 0.2s;
}
.placeholder-container:hover {
  background: #222;
  border-color: var(--primary-color);
}
.placeholder-icon {
  opacity: 0.5;
  transition: opacity 0.2s;
}
.placeholder-container:hover .placeholder-icon {
  opacity: 0.7;
}
.placeholder-text {
  color: #777;
  font-size: 1.1em;
  transition: color 0.2s;
}
.placeholder-container:hover .placeholder-text {
  color: #999;
}

.loading-overlay {
  position: absolute;
  inset: 0;
  background: rgba(42, 42, 42, 0.9);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 10;
  border-radius: 8px;
  color: #fff;
}
.progress-bar {
  width: 80%;
  height: 8px;
  background-color: #444;
  border-radius: 4px;
  overflow: hidden;
  margin: 1rem 0 0.5rem;
}
.progress {
  height: 100%;
  background-color: var(--primary-color, #ff6600);
  transition: width 0.1s linear;
}

.controls-area {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 1.5rem;
  background-color: var(--surface-color, #2a2a2a);
  border-radius: 12px;
  padding: 1rem 1.5rem;
  flex-wrap: wrap;
  align-items: center;
  border: 1px solid var(--border-color);
}

.control-group {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.player-controls {
  justify-content: flex-start;
}
.file-actions {
  justify-content: flex-end;
}
.game-settings {
  justify-content: center;
}

.play-pause-btn {
  background-color: var(--primary-color, #ff6600);
  border: none;
  border-radius: 50%;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  cursor: pointer;
  transition: all 0.2s;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.play-pause-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
  background-color: var(--primary-hover-color);
}
.play-pause-btn:disabled {
  background-color: #555;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.play-pause-btn svg {
  width: 28px;
  height: 28px;
}

.time-display {
  font-family: 'Roboto Mono', monospace;
  font-size: 1.1rem;
  color: var(--text-color);
  background-color: #1c1c1c;
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  min-width: 180px;
  text-align: center;
}

.volume-control {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.volume-control svg {
  color: var(--text-muted-color);
}
.volume-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100px;
  height: 5px;
  background: #444;
  border-radius: 5px;
  outline: none;
  opacity: 0.7;
  transition: opacity 0.2s;
}
.volume-slider:hover {
  opacity: 1;
}
.volume-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  background: var(--primary-color);
  cursor: pointer;
  border-radius: 50%;
}
.volume-slider::-moz-range-thumb {
  width: 15px;
  height: 15px;
  background: var(--primary-color);
  cursor: pointer;
  border-radius: 50%;
  border: none;
}

.setting-control {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.setting-control label {
  font-size: 0.9rem;
  color: var(--text-muted-color);
}
.settings-input {
  background-color: #333;
  border: 1px solid #444;
  color: var(--text-color);
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  width: 80px;
  text-align: center;
  transition:
    border-color 0.2s,
    box-shadow 0.2s;
}
.settings-input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(255, 102, 0, 0.2);
}

.action-btn {
  background-color: #3e3e3e;
  border: 1px solid #555;
  color: var(--text-color);
  padding: 0.65rem 1.25rem;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.95rem;
  font-weight: 500;
}
.action-btn:hover {
  background-color: #4f4f4f;
  border-color: #666;
}
.action-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background-color: #333;
}
.action-btn svg {
  opacity: 0.8;
}

.regions-list-container {
  background-color: var(--surface-color);
  padding: 1.5rem;
  border-radius: 12px;
  border: 1px solid var(--border-color);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.regions-list-container h2 {
  margin-top: 0;
  margin-bottom: 1.5rem;
  font-size: 1.5rem;
  font-weight: 600;
  color: #fff;
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 1rem;
}
.regions-list {
  list-style-type: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.region-item {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 1rem;
  background-color: #333;
  padding: 1rem 1.25rem;
  border-radius: 8px;
  border: 1px solid transparent;
  transition:
    background-color 0.2s,
    border-color 0.2s,
    box-shadow 0.3s;
  align-items: center;
}
.region-item:hover {
  background-color: #3e3e3e;
  border-color: #555;
}

.region-main-info {
  display: flex;
  align-items: center;
  gap: 1rem;
  cursor: pointer;
  grid-column: 1;
  grid-row: 1;
}
.region-index {
  background: #4f4f4f;
  color: #ccc;
  border-radius: 50%;
  width: 28px;
  height: 28px;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  font-weight: bold;
}
.region-details {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.timestamp {
  font-weight: 500;
  font-family: 'Roboto Mono', monospace;
}
.coords {
  font-size: 0.85em;
  color: var(--text-muted-color);
  display: flex;
  gap: 0.75rem;
}

.region-side-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  grid-column: 2;
  grid-row: 1;
}
.tag-input {
  background-color: #444;
  border: 1px solid #555;
  color: var(--text-color);
  border-radius: 6px;
  padding: 0.5rem;
  width: 60px;
  text-align: center;
  font-family: 'Roboto Mono', monospace;
}
.tag-input:focus {
  outline: none;
  border-color: var(--primary-color);
}
.region-setting {
  gap: 0.5rem;
}
.region-setting label {
  font-size: 0.9em;
  color: var(--text-muted-color);
}
.region-setting .settings-input {
  width: 60px;
  padding: 0.4rem;
  font-size: 0.9em;
}
.direction-btn-list {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: #444;
  border: 1px solid #555;
  color: #ccc;
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  cursor: pointer;
}
.direction-btn-list:hover {
  background-color: #555;
}

.region-event-area {
  grid-column: 1 / span 2;
  border-top: 1px solid #444;
  margin-top: 1rem;
  padding-top: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.event-tags {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}
.event-tag {
  background: #5a5a5a;
  color: #ddd;
  padding: 0.25rem 0.6rem;
  border-radius: 12px;
  font-size: 0.85em;
}
.no-events-text {
  color: #888;
  font-style: italic;
  font-size: 0.9em;
}
.manage-events-btn {
  background-color: #005a9e;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.9em;
  transition: background-color 0.2s;
}
.manage-events-btn:hover {
  background-color: #004a83;
}

.region-item.conflict-item {
  background-color: rgba(220, 38, 38, 0.15);
  border: 1px solid rgba(220, 38, 38, 0.4);
}

.region-item.has-error {
  border-color: var(--danger-color);
  box-shadow: 0 0 8px 0px var(--danger-color);
}

.error-indicator {
  font-size: 1.1em;
  line-height: 1;
  margin-right: 0.25rem;
  animation: pulse-warning 2s infinite;
}

@keyframes pulse-warning {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.2);
  }
}

.delete-btn {
  background: none;
  border: none;
  color: #888;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.2s;
  padding: 0 0.5rem;
  grid-column: 3;
  grid-row: 1;
}
.delete-btn:hover {
  color: var(--danger-hover-color);
}

/* MODAL STYLES */
.modal-fade-enter-active,
.modal-fade-leave-active {
  transition: opacity 0.3s ease;
}
.modal-fade-enter-active .modal-content,
.modal-fade-leave-active .modal-content {
  transition: all 0.3s ease;
}

.modal-fade-enter-from,
.modal-fade-leave-to {
  opacity: 0;
}

.modal-fade-enter-from .modal-content,
.modal-fade-leave-to .modal-content {
  transform: scale(0.95);
  opacity: 0;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.65);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  backdrop-filter: blur(5px);
}
.modal-content {
  background-color: var(--surface-color);
  padding: 0;
  border-radius: 12px;
  width: 90%;
  max-width: 650px;
  border: 1px solid var(--border-color);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  max-height: 90vh;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border-color);
  flex-shrink: 0;
}
.modal-header h3 {
  margin: 0;
  color: #fff;
  font-size: 1.25rem;
}
.modal-time-highlight {
  color: var(--primary-color);
  font-family: 'Roboto Mono', monospace;
}
.close-modal-btn {
  background: none;
  border: none;
  color: #888;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    color 0.2s,
    background-color 0.2s;
}
.close-modal-btn:hover {
  color: #fff;
  background-color: #444;
}

.modal-event-list {
  flex-grow: 1;
  overflow-y: auto;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
.no-events-modal {
  text-align: center;
  color: var(--text-muted-color);
  padding: 3rem 1rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 1rem;
}
.no-events-modal svg {
  stroke: #555;
}
.no-events-modal p {
  margin: 0;
  font-size: 1.1em;
}

.modal-event-item {
  background: #202020;
  padding: 1.5rem;
  border-radius: 8px;
  border: 1px solid #444;
  transition:
    background-color 0.2s,
    border-color 0.2s;
}

.modal-event-item:hover {
  background-color: #2c2c2c;
  border-color: #555;
}

.modal-event-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}
.modal-event-header h4 {
  margin: 0;
  color: var(--primary-color);
  font-size: 1.2em;
}
.remove-event-item-btn {
  background: none;
  border: none;
  color: #888;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem;
  border-radius: 50%;
  transition:
    color 0.2s,
    background-color 0.2s;
}
.remove-event-item-btn:hover {
  color: var(--danger-color);
  background-color: rgba(239, 68, 68, 0.1);
}

.form-group {
  margin-bottom: 1rem;
}
.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--text-muted-color);
  font-size: 0.9em;
}
.modal-input,
.modal-select,
.modal-textarea {
  width: 100%;
  background-color: #333;
  border: 1px solid #444;
  color: var(--text-color);
  border-radius: 6px;
  padding: 0.75rem;
  font-size: 1em;
  box-sizing: border-box;
  font-family: inherit;
  transition: all 0.2s;
}
.modal-textarea {
  resize: vertical;
  font-family: 'Roboto Mono', monospace;
}
.modal-input:focus,
.modal-select:focus,
.modal-textarea:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(255, 102, 0, 0.2);
}
.payload-error {
  color: var(--danger-color);
  font-size: 0.85em;
  margin-top: 0.5rem;
}
.invalid-json {
  border-color: var(--danger-color) !important;
}
.invalid-json:focus {
  box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.3) !important;
}

.modal-actions {
  margin-top: auto;
  padding: 1.5rem;
  border-top: 1px solid var(--border-color);
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: #252525;
  flex-shrink: 0;
}
.modal-btn {
  padding: 0.6rem 1.2rem;
  border-radius: 6px;
  border: none;
  cursor: pointer;
  font-weight: 500;
  transition: all 0.2s;
}
.modal-btn-primary {
  background-color: var(--primary-color);
  color: white;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.modal-btn-primary:hover {
  background-color: var(--primary-hover-color);
}

.modal-btn-secondary {
  background-color: #4f4f4f;
  color: var(--text-color);
  border: 1px solid #666;
}

.modal-btn-secondary:hover {
  background-color: #5a5a5a;
}

/* Dropdown Styles */
.dropdown {
  position: relative;
  display: inline-block;
}
.dropdown-content {
  display: none;
  position: absolute;
  background-color: #3e3e3e;
  min-width: 180px;
  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.5);
  z-index: 1001;
  bottom: 100%;
  margin-bottom: 5px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border-color);
  padding: 0.25rem 0;
}
.dropdown.is-active .dropdown-content {
  display: block;
}
.dropdown-content a {
  color: var(--text-color);
  padding: 10px 16px;
  text-decoration: none;
  display: block;
  cursor: pointer;
}
.dropdown-content a:hover {
  background-color: var(--primary-color);
  color: #fff;
}
</style>
